﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;

using RFEngine.Audio.Internal;

namespace RFEngine.Audio
{
    public class Sound : ISound
    {
        #region Fields

        private string _Name;
        private byte[] _SoundData;
        
        private uint _Version;        

        private AudioState _AudioState = AudioState.Stopped;
         
        protected uint _Position;
        protected uint _Length;
        protected float _BaseFrequency;
        protected float _BaseVolume = 1.0f;        

        protected Internal.System _System = null;
        protected Internal.Sound _Sound = null;
        protected Channel _Channel = null;

        #endregion

        #region Properties

        /// <summary>
        /// Gets the name of this object
        /// </summary>
        public virtual string Name
        {
            get { return _Name; }
        }

        /// <summary>
        /// Gets the Audio State of this object.
        /// </summary>
        public virtual AudioState AudioState
        {
            get { return _AudioState; }
        }

        #endregion

        #region Methods

        #region Constructors

        /// <summary>
        /// Constructs a new instance of this object.
        /// </summary>
        /// <param name="name">The string value of the name of this object.</param>
        /// <param name="fileLocation">The file location to load</param>
        internal Sound(string name, string fileLocation)
        {
            _Name = name;
            _SoundData = null;

            ERRCHECK(Factory.System_Create(ref _System));
            ERRCHECK(_System.getVersion(ref _Version));
            ERRCHECK(_System.init(32, INITFLAGS.NORMAL, (IntPtr)null));

            ERRCHECK(_System.createSound(fileLocation, (MODE._3D | MODE.HARDWARE | MODE.CREATESTREAM), ref _Sound));
        }

        /// <summary>
        /// Constructs a new instance of this object.
        /// </summary>
        /// <param name="name">The string value of the name of this object.</param>
        /// <param name="soundData">The sound data to load</param>
        internal Sound(string name, byte[] soundData)
        {
            _Name = name;
            _SoundData = soundData;

            #region Initializer

            ERRCHECK(Factory.System_Create(ref _System));
            ERRCHECK(_System.getVersion(ref _Version));
            ERRCHECK(_System.init(32, INITFLAGS.NORMAL, (IntPtr)null));

            Internal.CREATESOUNDEXINFO exinfo = new Internal.CREATESOUNDEXINFO();

            exinfo.cbsize = Marshal.SizeOf(exinfo);
            exinfo.length = (uint)_SoundData.Length;
            exinfo.numchannels = 32;

            ERRCHECK(_System.createSound(soundData, MODE._3D | MODE.HARDWARE | MODE.OPENMEMORY, ref exinfo, ref _Sound));

            #endregion
        }        

        #endregion
        
        /// <summary>
        /// Method used for pausing this sound.
        /// </summary>
        public virtual void Pause()
        {
            if (_AudioState == AudioState.Playing)
            {
                _AudioState = AudioState.Paused;
                this._Channel.setPaused(true);
            }
        }

        /// <summary>
        /// Method used for playing this sound.
        /// </summary>
        public virtual void Play()
        {
            if (AudioState == AudioState.Paused)
                Resume();

            if (AudioState != AudioState.Playing)
            {
                _System.playSound(CHANNELINDEX.REUSE, _Sound, false, ref _Channel);

                if (AudioState == AudioState.Stopped || AudioState == AudioState.Completed)
                    _Channel.setPosition(0, TIMEUNIT.MS);

                _Channel.getFrequency(ref _BaseFrequency);
                _Channel.getVolume(ref _BaseVolume);
                _Channel.getPosition(ref _Position, TIMEUNIT.MS);
                _Sound.getLength(ref _Length, TIMEUNIT.MS);

                _AudioState = AudioState.Playing;
            }            
        }

        /// <summary>
        /// Method used for Resuming a Paused Sound.
        /// </summary>
        public virtual void Resume()
        {
            if (AudioState == AudioState.Paused)
            {
                this._Channel.setPosition(_Position, TIMEUNIT.MS);
                this._Channel.setPaused(false);
                _AudioState = AudioState.Playing;
            }
        }

        /// <summary>
        /// Method used for stoping this sound.
        /// </summary>
        public virtual void Stop()
        {
            if (AudioState != AudioState.Completed || AudioState != AudioState.Stopped)
            {
                this._Channel.stop();
                _AudioState = AudioState.Stopped;
            }
        }

        #region Private Methods

        internal virtual void Update() { }

        void ISound.Update()
        {
            Update();
        }

        private void ERRCHECK(RESULT result)
        {
            if (result != Internal.RESULT.OK)
            {
                throw new InvalidProgramException("Problem with FMOD " + Environment.NewLine + result + ": ");
            }
        }

        #endregion

        #region Protected Methods
        
        /// <summary>
        /// Method used to remove all unmanaged memory from this instance.
        /// </summary>
        protected virtual void Destroy()
        {
            _Channel.stop();
            ERRCHECK(_Sound.release());
            ERRCHECK(_System.release());

            GC.SuppressFinalize(this);
        }

        void ISound.Destroy()
        {
            Destroy();
        }

        #endregion

        #region Static Methods

        private static Byte[] GetSoundData(string fileLocation)
        {
            byte[] soundData;

            using (FileStream fs = new FileStream(@fileLocation, FileMode.Open, FileAccess.Read))
            {
                soundData = new byte[fs.Length];
                fs.Read(soundData, 0, (int)fs.Length);
            };

            return soundData;
        }

        #endregion

        #endregion
    }
}
